package com.example.bluetoothlib.model;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.Toast;

public class BluetoothHelper 
{
	static final String DEBUG_TAG="BluetoothHelper";
	BluetoothAdapter bluetoothAdapter;
	boolean isBTDeviceExists=false;
	private static BluetoothHelper instance;
	private BroadcastReceiver broadcastReceiver;
	private ArrayList<BluetoothDevice> foundDevices;
	
	private AcceptThread acceptThread;
	private ConnectThread connectThread;
	
	private BTConnectionManager btConnectionManager;
	private IBTEventHandler eventHandler;
	private BluetoothHelper()
	{
		btConnectionManager=new BTConnectionManager();
		bluetoothAdapter=BluetoothAdapter.getDefaultAdapter();
		if(null==bluetoothAdapter)//doesn't have bt device
		{
			isBTDeviceExists=false;
		}
		else
		{
			isBTDeviceExists=true;
		}
	}
	
	public static BluetoothHelper getInstance()
	{
		if(null==instance)
			instance=new BluetoothHelper();
		return instance;
	}
	public void setEventHandler(IBTEventHandler event)
	{
		this.eventHandler=event;
		btConnectionManager.setEventHandler(event);
	}
	public void promptToEnableBT(Activity activity,int requestCode)
	{
		if(!bluetoothAdapter.isEnabled())
		{
			Intent intent=new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
			activity.startActivityForResult(intent, requestCode);
		}
	}
	
	public void promptToEnableDiscoverability(Activity activity,int requestCode)
	{
		Intent discoverableIntent = new	Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
		discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
		activity.startActivityForResult(discoverableIntent,requestCode);
	}
	
	public void searchDevices()
	{
		bluetoothAdapter.startDiscovery();
	}
	
	public void registerBroadcastReceiver(final Activity activity)
	{
		foundDevices=new ArrayList<BluetoothDevice>();
		broadcastReceiver=new BroadcastReceiver() {
			
			@Override
			public void onReceive(Context context, Intent intent) {
				if(BluetoothDevice.ACTION_FOUND.equals(intent.getAction()))
				{
					foundDevices.add((BluetoothDevice) intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
				}
				else if(BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(intent.getAction()))
				{
					eventHandler.onDiscoverFinished(foundDevices);
					Toast.makeText(activity, "discover finished", Toast.LENGTH_SHORT).show();
					for(int i=0;i<foundDevices.size();i++)
					{
						Toast.makeText(activity, foundDevices.get(i).getName()+":"+
								foundDevices.get(i).getAddress(), Toast.LENGTH_SHORT).show();
					}
				}
				else if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(intent.getAction())) 
				{
					eventHandler.onDiscoverStart();
					Toast.makeText(activity, "Start discover", Toast.LENGTH_SHORT).show();
				}
			}
		};
		IntentFilter intentFilter=new IntentFilter(BluetoothDevice.ACTION_FOUND);
		intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
		intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
		activity.registerReceiver(broadcastReceiver, intentFilter);
	}
	
	public void unregisterBroadcastReceiver(Activity activity)
	{
		activity.unregisterReceiver(broadcastReceiver);
	}
	
	public void listenForConnections()
	{
		if(null==acceptThread)
		{
			acceptThread=new AcceptThread();
			acceptThread.execute();
		}
		else if(!acceptThread.getStatus().equals(AsyncTask.Status.RUNNING))
		{
			acceptThread.execute();
		}
	}
	
	public void connectToServer(int position)
	{
		if(null==connectThread)
		{
			if(0>=position&&position<foundDevices.size())
			{
				connectThread=new ConnectThread(foundDevices.get(position));
				connectThread.execute();
			}
			else
			{
				Log.e(DEBUG_TAG, "Error position:"+position);
			}
		}
		else if(!connectThread.getStatus().equals(AsyncTask.Status.RUNNING))
		{
			connectThread.execute();
		}
	}
	public String readMessage()
	{
		return btConnectionManager.readString();
	}
	public void sendMessage(String msg)
	{
		btConnectionManager.writeString(msg);
	}
	private class AcceptThread extends AsyncTask<Void, Void, BluetoothSocket> 
	{
	    private BluetoothServerSocket mmServerSocket;

		@Override
		protected BluetoothSocket doInBackground(Void... params) 
		{
			try {
	            // MY_UUID is the app's UUID string, also used by the client code
	        	mmServerSocket = bluetoothAdapter.listenUsingRfcommWithServiceRecord("BTServer", Constants.MY_APP_UUID);
	        } catch (IOException e) { e.printStackTrace();}
	        BluetoothSocket socket = null;
	        // Keep listening until exception occurs or a socket is returned
	        while (true) {
	            try {
	                socket = mmServerSocket.accept();
	            } catch (IOException e) {
	                break;
	            }
	            // If a connection was accepted
	            if (socket != null) {
	            	return socket;
	            }
	        }
			return null;
		}

		@Override
		protected void onPostExecute(BluetoothSocket result) 
		{
			btConnectionManager.establishConnection(result);
			super.onPostExecute(result);
		}
	}
	
	private class ConnectThread extends AsyncTask<Void, Void, BluetoothSocket> 
	{  
        private BluetoothSocket mmSocket;  
        private final BluetoothDevice mmDevice;  
      
        public ConnectThread(BluetoothDevice device) 
        {
            mmDevice = device;   
        }  
      
        private void tryConnectUsingReflection() throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException
        {
        	Class<?> clazz = mmDevice.getClass();
            Class<?>[] paramTypes = new Class<?>[] {Integer.TYPE};
            Method m = clazz.getMethod("createRfcommSocket", paramTypes);
            Object[] params = new Object[] {Integer.valueOf(1)};
            mmSocket = (BluetoothSocket) m.invoke(mmSocket.getRemoteDevice(), params);
        }

		@Override
		protected BluetoothSocket doInBackground(Void... params) {
			// Get a BluetoothSocket to connect with the given BluetoothDevice  
            try {  
                // MY_UUID is the app's UUID string, also used by the server code  
            	mmSocket = mmDevice.createRfcommSocketToServiceRecord(Constants.MY_APP_UUID);  
                
            } catch (IOException e) {e.printStackTrace(); } 
            // Cancel discovery because it will slow down the connection  
            bluetoothAdapter.cancelDiscovery();  
      
            try {  
                // Connect the device through the socket. This will block  
                // until it succeeds or throws an exception  
                mmSocket.connect();  
            } catch (IOException connectException) {  
                // Unable to connect; try using reflection
                try {   
                	tryConnectUsingReflection();
                } catch (NoSuchMethodException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
				} catch (InvocationTargetException e) {
					e.printStackTrace();
				}  
            }  
            return mmSocket;
		}  
		@Override
		protected void onPostExecute(BluetoothSocket result) {
			btConnectionManager.establishConnection(result);
			super.onPostExecute(result);
		}
    }
}
